#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Google
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
<%= lines(autogen_notice(:python, pwd)) -%>

from __future__ import absolute_import, division, print_function
__metaclass__ = type

<%= lines(compile(pwd + '/templates/ansible/documentation.erb'), 1) -%>
################################################################################
# Imports
################################################################################
<%
  readonly_selflink_rrefs = object.all_resourcerefs
                                  .select { |prop| prop.resource_ref.readonly }
                                  .select { |prop| prop.imports == 'selfLink' }
                                  .map(&:resource_ref)
                                  .uniq

  update_props = properties_by_custom_update(object.all_user_properties, :old)
-%>

<%
  import = "from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest"
  import += ', remove_nones_from_dict' unless properties_with_classes(object.all_user_properties).empty?
  import += ', replace_resource_dict' if nonreadonly_rrefs(object)
-%>
<%= lines(import) -%>
import json
<%
  imports = object.imports || []
  imports << 'time' if object.async
  imports << 're' if !readonly_selflink_rrefs.empty? || object.all_user_properties.any?(&:pattern)
-%>
<%= lines(imports.sort.uniq.map { |i| "import #{i}" }) -%>

################################################################################
# Main
################################################################################


def main():
    """Main function"""

<%= lines(indent(compile(pwd + '/templates/ansible/module.erb'), 4)) -%>

    if not module.params['scopes']:
        module.params['scopes'] = <%= python_literal(object.__product.scopes) %>

    state = module.params['state']
<% if object.kind? -%>
    kind = <%= lines(quote_string(object.kind)) -%>
<% end -%>

<% if object.nested_query.nil? -%>
<%
  method = method_call('fetch_resource', ['module', 'self_link(module)',
                                          ('kind' if object.kind?),
                                         ])
-%>
<% if object.identity.empty? -%>
    if module.params['id']:
        fetch = <%= method %>
    else:
        fetch = {}
<% else #object.identity.empty? -%>
    fetch = <%= method %>
<% end # object.identity.empty? -%>
<% else # object.nested_query.nil? -%>
    fetch = fetch_wrapped_resource(module, '<%= object.kind -%>',
                                   '<%= object.nested_query.kind -%>',
                                   '<%= object.nested_query.keys.first -%>')
<% end # object.nested_query.nil? -%>
    changed = False
<% if object.custom_code.pre_action -%>

<%= lines(indent(object.custom_code.pre_action, 4)) -%>
<% end # if object.custom_code.pre_action -%>

    if fetch:
        if state == 'present':
            if is_different(module, fetch):
<%
  method = method_call('update', [
                                   'module', 'self_link(module)',
                                   ('kind' if object.kind?),
                                   ('fetch' if object.access_api_results || !update_props.empty? || object.update_mask)
                                 ])
-%>
<%= lines(indent("#{method}", 16)) -%>
<%
  method = method_call('fetch_resource', ['module', 'self_link(module)',
                                          ('kind' if object.kind?),
                                         ])
-%>
                fetch = <%= method %>
                changed = True
        else:
<%
  if object.delete_url.nil?
    delete_url = 'self_link(module)'
  else
    delete_url = 'delete_link(module)'
  end
  method = method_call('delete', [
                                   'module', delete_url,
                                   ('kind' if object.kind?),
                                   ('fetch' if object.access_api_results)
                                 ])
-%>
<%= lines(indent(method, 12)) -%>
            fetch = {}
            changed = True
    else:
        if state == 'present':
<%
  if !object.create_url.nil?
    create_link = 'create_link(module)'
  elsif object.create_verb.nil? || object.create_verb == :POST
      create_link = 'collection(module)'
  elsif object.create_verb == :PUT || object.create_verb == :PATCH
      create_link = 'self_link(module)'
  end

  create_verb = ".#{object.create_verb.to_s.downcase}"
  method = method_call('create', ['module', create_link,
                                  ('kind' if object.kind?)])
-%>
            fetch = <%= method %>
<% if object.custom_code.post_create -%>
<%= lines(indent(object.custom_code.post_create, 12)) -%>
<% end # ifobject.custom_code.post_create -%>
            changed = True
        else:
            fetch = {}

<% if object.custom_code.post_action -%>
<%= lines(indent(object.custom_code.post_action, 4)) -%>
<% end # ifobject.custom_code.post_create -%>
    fetch.update({'changed': changed})

    module.exit_json(**fetch)


<% prod_name = object.__product.api_name -%>
<%# TODO: kind param not always needed.
  # https://github.com/GoogleCloudPlatform/magic-modules/issues/45
-%>
<%= method_decl('create', ['module', 'link', ('kind' if object.kind?)]) %>
<% if object.custom_code.create.nil? -%>
    auth = GcpSession(module, <%= quote_string(prod_name) -%>)
<%
  create_request = if object.custom_code.custom_create_resource
                     'resource_to_create'
                   else
                     'resource_to_request'
                   end
-%>
<%
  method = method_call(
    object.async && object.async.allow?('create') ? 'wait_for_operation' : 'return_if_object',
    ['module',
     method_call("auth#{create_verb}",
                 ['link', "#{create_request}(module)"]),
     ('kind' if !object.async && object.kind?)
   ]
  )
-%>
    return <%= method %>
<% else -%>
<%= lines(indent(object.custom_code.create, 4)) -%>
<% end -%>


<%=
  lines(method_decl('update', ['module', 'link', ('kind' if object.kind?),
                               ('fetch' if object.access_api_results || !update_props.empty? || object.update_mask)]))
-%>
<%
  update_request = if object.custom_code.custom_update_resource
                     'resource_to_update'
                   else
                     'resource_to_request'
                   end
-%>
<% if object.custom_code.update.nil? -%>
<% unless update_props.empty? -%>
    update_fields(module, resource_to_request(module),
                  response_to_hash(module, fetch))
<% end -%>
<%   if object.input && update_props.empty? -%>
<%=
    indent(method_call('delete', [
                            'module', delete_url,
                            ('kind' if object.kind?),
                            ('fetch' if object.access_api_results)
                          ]), 4)
%>
<%=
  indent(method_call('create', ['module', create_link,
                         ('kind' if object.kind?)]), 4)
%>
<%   elsif  !object.input -%>
    auth = GcpSession(module, <%= quote_string(prod_name) -%>)
<% method_args = ['link'] -%>
<% if object.update_mask -%>
    params = {
        'updateMask': updateMask(resource_to_request(module), response_to_hash(module, fetch))
    }
    request = resource_to_request(module)
<% unless object.all_user_properties.select { |p| p.name == 'name' }.first&.url_param_only -%>
    del request['name']
<% end -%>
<%
  method_args.append("request")
  method_args.append("params=params")
-%>
<% else # update mask -%>, 
<% method_args.append("#{update_request}(module)") -%>
<% end # update mask -%>
<%
  update_verb = object.update_verb.to_s.downcase
  method = method_call(
    object.async && object.async.allow?('update') ? 'wait_for_operation' : 'return_if_object',
    [
     'module',
     method_call("auth.#{update_verb}", method_args),
     ('kind' if !object.async && object.kind?)
   ]
  )
-%>
    return <%= method %>
<% else # non-updatable, but has per-field updates -%>
<%
  method = method_call('fetch_resource', ['module', 'self_link(module)',
                                          ('kind' if object.kind?),
                                         ])
-%>
    return <%= method %>
<%   end # if object.input -%>
<% else # object.custom_code.update.nil? -%>
<%= lines(indent(object.custom_code.update, 4)) -%>
<% end # object.custom_code.update.nil? -%>


<% unless update_props.empty? -%>
def update_fields(module, request, response):
<% empty = update_props.values.flatten.reject { |p| p.is_a? Api::Type::FetchedExternal }.empty? -%>
<% if empty -%>
    pass
<% else -%>
<% update_props.each do |key, props| -%>
<%
  props_statement = props.reject { |p| p.is_a?(Api::Type::FetchedExternal) }
  .map { |prop| "response.get(#{quote_string(prop.api_name)}) != request.get(#{quote_string(prop.api_name)})" }.join(' or ')
-%>
<% unless props_statement.empty? -%>
    if <%= props_statement -%>:
        <%= props.first.name.underscore -%>_update(module, request, response)
<% end # unless props_statement.empty? -%>
<% end # update_props.each -%>
<% end # if empty -%>


<% update_props.each do |key, props| -%>
<% func_name = "#{props.first.name.underscore}_update" -%>
<% unless object.hidden.include?(func_name) -%>
def <%= func_name -%>(module, request, response):
    auth = GcpSession(module, <%= quote_string(prod_name) -%>)
<% if key[:fingerprint_name] -%>
    new_resource = <%= method_call('fetch_resource', ['module', 'self_link(module)',
                                          ('kind' if object.kind?),
                                          ])
                    %>
    fingerprint = new_resource[:<%= key[:fingerprint_name] -%>]
<% end -%>
<%
    request_props = if key[:fingerprint_name]
                      request_properties(props).merge({fingerprint_name => Google::PythonUtils::PythonCode.new("new_resource[:#{key[:fingerprint_name]}]")})
                    else
                      request_properties(props)
                    end
-%>
    auth.<%= key[:update_verb].downcase -%>(
        ''.join([
            "<%= object.__product.base_url -%>",
            "<%= key[:update_url].gsub('{{', '{').gsub('}}', '}') -%>"
        ]).format(**module.params),
<%= lines(python_literal(request_props, use_hash_brackets: true)) -%>
    )

<% end # unless object.hidden.include? -%>
<% end -%>
<% end # unless update_props.empty? -%>
<% if object.update_mask -%>
<%= lines(compile(pwd + '/templates/ansible/update_mask.erb')) -%>
<% end # if update_mask -%>
<%=
  lines(method_decl('delete', ['module', 'link', ('kind' if object.kind?),
                               ('fetch' if object.access_api_results)]))
-%>
<% if object.custom_code.delete.nil? -%>
    auth = GcpSession(module, <%= quote_string(prod_name) -%>)
<%
  delete_verb = object.delete_verb.to_s.downcase

  method = method_call(
    object.async && object.async.allow?('delete') ? 'wait_for_operation' : 'return_if_object',
    ['module',
     method_call("auth.#{delete_verb}", ['link']),
     ('kind' if !object.async && object.kind?)
   ]
  )
-%>
    return <%= method %>
<% else # if object.custom_code.delete.nil? -%>
<%= lines(indent(object.custom_code.delete, 4)) -%>
<% end # if object.custom_code.delete.nil? -%>


def resource_to_request(module):
<%
  properties_in_request = [
    object&.parameters&.select { |p| p.input },
    object.properties.reject(&:output)
  ].flatten.compact.reject(&:url_param_only)
-%>
<%
  request_hash = {
    Google::PythonUtils::UnicodeString.new('kind') => object.kind
}.merge(request_properties(properties_in_request))
-%>
    request = <%= lines(python_literal(request_hash, use_hash_brackets: true)) -%>
<% if object.encoder? -%>
    request = <%= object.transport.encoder -%>(request, module)
<% end -%>
<% if !object.transport or object&.transport&.remove_nones_post_encoder -%>
    return_vals = {}
    for k, v in request.items():
        if v or v is False:
            return_vals[k] = v

    return return_vals
<% else -%>
    return request
<% end -%>
<% if object.unwrap_resource -%>
<%
  urf_code = [
    'return {',
    indent_list(
      Hash[object.identity.map { |i| [i, "module.params[#{quote_string(i.out_name)}]"] }]
        .map { |k, v| "#{quote_string(k.out_name)}: #{v}" }, 4
    ),
    '}'
  ]
-%>
<%=
  lines_before(lines(emit_method('unwrap_resource_filter', %w[module],
                                 urf_code, file_relative), 1), 1)
-%>

def unwrap_resource(result, module):
    query_predicate = unwrap_resource_filter(module)
    matched_items = []
    for item in result:
        if all(item[k] == query_predicate[k] for k in query_predicate.keys()):
            matched_items.append(item)
    if len(matched_items) > 1:
        module.fail_json(msg="More than 1 result found: %s" % matched_items)

    if matched_items:
        return matched_items[0]
    else:
        return None
<% end -%>


<%= lines(compile(pwd + '/templates/ansible/transport.erb'), 2) -%>
<%= lines(emit_link('self_link', build_url(object.self_link_url), object), 2) -%>
<%= lines(emit_link('collection', build_url(object.collection_url), object), 2) -%>
<%- unless object.create_url.nil? -%>
<%=lines(emit_link('create_link', build_url(object.full_create_url), object), 2)-%>
<% end -%>
<% unless object.delete_url.nil? -%>
<%= lines(emit_link('delete_link', build_url(object.full_delete_url), object), 2) -%>
<% end -%>
<%=
  lines(method_decl('return_if_object', ['module', 'response',
                                         ('kind' if object.kind?),
                                         'allow_not_found=False']))
-%>
<% if object.custom_code.return_if_object -%>
<%= lines(indent(object.custom_code.return_if_object, 4)) -%>
<% else # if object.custom_code.return_if_object -%>
    # If not found, return nothing.
    if allow_not_found and response.status_code == 404:
        return None

    # If no content, return nothing.
    if response.status_code == 204:
        return None

    try:
        module.raise_for_status(response)
        result = response.json()
    except getattr(json.decoder, 'JSONDecodeError', ValueError):
        module.fail_json(msg="Invalid JSON response with error: %s" % response.text)

<% if object.decoder? -%>
    result = <%= object.transport.decoder -%>(result, module)

<% end -%>
    if navigate_hash(result, ['error', 'errors']):
        module.fail_json(msg=navigate_hash(result, ['error', 'errors']))

    return result
<% end # if object.custom_code.return_if_object -%>


def is_different(module, response):
    request = resource_to_request(module)
    response = response_to_hash(module, response)
<% if object.decoder? -%>
    request = <%= object.transport.decoder -%>(request, module)
<% end -%>

    # Remove all output-only from response.
    response_vals = {}
    for k, v in response.items():
        if k in request:
            response_vals[k] = v

    request_vals = {}
    for k, v in request.items():
        if k in response:
            request_vals[k] = v

    return GcpRequest(request_vals) != GcpRequest(response_vals)


# Remove unnecessary properties from the response.
# This is for doing comparisons with Ansible's current parameters.
def response_to_hash(module, response):
    return <%= lines(python_literal(response_properties(object.properties), use_hash_brackets: true)) -%>
<% if object.all_user_properties.any?(&:pattern) -%>
<%= lines(compile(pwd + '/templates/ansible/pattern.py.erb')) -%>
<% end -%>
<% readonly_selflink_rrefs.each do |resource| -%>


def <%= resource.name.underscore -%>_selflink(name, params):
    if name is None:
        return
    url = r<%= lines(build_url(regex_url(resource.self_link_url))) -%>
    if not re.match(url, name):
        name = <%= build_url(resource.self_link_url).gsub('{name}', '%s') -%>.format(**params) % name
    return name
<% end -%>
<%= lines_before(compile(pwd + '/templates/ansible/async.erb'), 1) -%>
<%= lines_before(compile(pwd + '/templates/ansible/provider_helpers.erb'), 1) -%>
<%= lines_before(compile(pwd + '/templates/ansible/properties.erb'), 1) -%>


if __name__ == '__main__':
    main()
